/*
 * Ingenic libsysutils cipher implement.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Elvis <huan.wang@ingenic.com>
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <pthread.h>

#include <sysutils/su_cipher.h>
#include <imp/imp_log.h>
#include "cipher.h"

#define TAG		"Cipher"

#define CIPHER_PATH			"/dev/aes"
#define PROCESSING_SIZE		1024*1024
#define SRC_BUF_OFF			0
#define DST_BUF_OFF			1024*1024

static pthread_mutex_t cipher_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t handle_mutex = PTHREAD_MUTEX_INITIALIZER;

static int cipher_fd = -1;
static int handle = -1;
static int mem_fd;
static struct aes_para para[16];
static int count = 0;
unsigned int *mem_user_v	= NULL;

int SU_CIPHER_Init(void)
{
	int i = 0;

	if (cipher_fd >= 0) {
		IMP_LOG_DBG(TAG, "you have already init cipher before!\n");
		return REINIT;
	}

	cipher_fd = open(CIPHER_PATH, O_RDONLY);
	if(cipher_fd < 0) {
		IMP_LOG_DBG(TAG, "open() error !\n");
		return INIT_FAILED;
	}

	if (ioctl(cipher_fd, IOCTL_AES_GET_PBUFF, &para[0].mem_p)) {
		IMP_LOG_DBG(TAG, "ioctl :IOCTL_AES_GET_PBUFF error!\n");
		return INIT_FAILED;
	}

	mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
	if ( mem_fd < 0 ) {
		IMP_LOG_DBG(TAG, "open /dev/mem failed.\n");
		return INIT_FAILED;
	}

	mem_user_v = mmap(NULL, PROCESSING_SIZE * 2, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, para[0].mem_p & 0x1fffffff);

	for (i = 0; i < 16; i++) {
		para[i].src_addr_v = (unsigned char *)mem_user_v + SRC_BUF_OFF;
		para[i].dst_addr_v = (unsigned char *)mem_user_v + DST_BUF_OFF;
		para[i].src_addr_p = para[0].mem_p + SRC_BUF_OFF;
		para[i].dst_addr_p = para[0].mem_p + DST_BUF_OFF;
	}

	return 0;
}

int SU_CIPHER_Exit(void)
{
	if (cipher_fd >= 0) {
		if (count == 0) {
			close(cipher_fd);
			close(mem_fd);
			munmap(mem_user_v, PROCESSING_SIZE * 2);
			mem_user_v = NULL;
		} else {
			IMP_LOG_DBG(TAG, "Cipher module are uesd to other module now!\n");
			return EXIT_ERR;
		}
	} else {
		IMP_LOG_DBG(TAG, "you aren't init cipher before!\n");
		return UNINIT;
	}

	return 0;
}

int SU_CIPHER_CreateHandle(void)
{
	int i = 0, ihandle = -1;
	pthread_mutex_lock(&handle_mutex);

	if (cipher_fd >= 0) {
		for (i = 0; i <= handle; i++) {
			if (para[i].status == STATUS_FREE) {
				para[i].status = STATUS_USED;
				ihandle = i;
				count++;
				goto find;
			}
		}

		handle += 1;
		ihandle = handle;
		para[ihandle].status = STATUS_USED;
		count++;
	} else {
		IMP_LOG_DBG(TAG, "%s not open,CreateHandle error\n", CIPHER_PATH);
		return FAILED_GETHANDLE;
	}

find:
	pthread_mutex_unlock(&handle_mutex);

	return ihandle;
}

int SU_CIPHER_DestroyHandle(int fd)
{
	pthread_mutex_lock(&handle_mutex);
	if (count != 0) {
		para[fd].status = STATUS_FREE;
		count--;
	} else {
		IMP_LOG_DBG(TAG, "There is no handle need destroy!\n");
		return FAILED_DESHANDLE;
	}
	pthread_mutex_unlock(&handle_mutex);

	return 0;
}

static int SU_CIPHER_Setpara(int hCipher, IN_UNF_CIPHER_CTRL* Ctrl)
{
	if (Ctrl == NULL) {
		IMP_LOG_DBG(TAG, "para is NULL, set para error!\n");
		return INVALID_PARA;
	}

	para[hCipher].aeskey[0] = Ctrl->key[0];
	para[hCipher].aeskey[1] = Ctrl->key[1];
	para[hCipher].aeskey[2] = Ctrl->key[2];
	para[hCipher].aeskey[3] = Ctrl->key[3];
	para[hCipher].aesiv[0]  = Ctrl->IV[0];
	para[hCipher].aesiv[1]  = Ctrl->IV[1];
	para[hCipher].aesiv[2]  = Ctrl->IV[2];
	para[hCipher].aesiv[3]  = Ctrl->IV[3];
	para[hCipher].enAlg		= Ctrl->enAlg;
	para[hCipher].enBitWidth= Ctrl->enBitWidth;
	para[hCipher].enWorkMode= Ctrl->enWorkMode;
	para[hCipher].enKeyLen  = Ctrl->enKeyLen;
	para[hCipher].dataLen   = Ctrl->enDataLen;

	return 0;
}

int SU_CIPHER_ConfigHandle(int hCipher, IN_UNF_CIPHER_CTRL* Ctrl)
{
	int ret;

	ret = SU_CIPHER_Setpara(hCipher, Ctrl);
	if (ret)
		return SET_PARA_FAILED;

	return 0;
}

static int SU_CIPHER_ConfigPara(int hCipher)
{
	if (ioctl(cipher_fd, IOCTL_AES_SET_PARA, &para[hCipher])) {
		IMP_LOG_DBG(TAG, "ioctl :IOCTL_AES_SET_PARA error!\n");
		return SET_PARA_FAILED;
	}

	return 0;
}

int SU_CIPHER_Encrypt(int hCipher, unsigned int * srcAddr, unsigned int * dstAddr, unsigned int dataLen)
{
	if ((dataLen > PROCESSING_SIZE) || (dataLen < 16)) {
		IMP_LOG_DBG(TAG, "dataLen is not suitable, 16 <= dataLen <= 1024*1024\n");
		return SET_DATALEN_ERR;
	}

	pthread_mutex_lock(&cipher_mutex);

	para[hCipher].dataLen = dataLen;
	if (SU_CIPHER_ConfigPara(hCipher))
		return SET_PARA_FAILED;

	memcpy(para[hCipher].src_addr_v, srcAddr, para[hCipher].dataLen);
	if (ioctl(cipher_fd, IOCTL_AES_START_EN_PROCESSING, NULL)) {
		IMP_LOG_DBG(TAG, "ioctl :IOCTL_AES_START_EN_PROCESSING error!\n");
		return FAILURE;
	}
	memcpy(dstAddr, para[hCipher].dst_addr_v, para[hCipher].dataLen);

	pthread_mutex_unlock(&cipher_mutex);

	return 0;
}

int SU_CIPHER_Decrypt(int hCipher, unsigned int * srcAddr, unsigned int * dstAddr, unsigned int dataLen)
{
	if ((dataLen > PROCESSING_SIZE) || (dataLen < 16)) {
		IMP_LOG_DBG(TAG, "dataLen is not suitable, 16 <= dataLen <= 1024*1024\n");
		return SET_DATALEN_ERR;
	}

	pthread_mutex_lock(&cipher_mutex);

	para[hCipher].dataLen = dataLen;
	if (SU_CIPHER_ConfigPara(hCipher))
		return SET_PARA_FAILED;

	memcpy(para[hCipher].src_addr_v, srcAddr, para[hCipher].dataLen);
	if (ioctl(cipher_fd, IOCTL_AES_START_DE_PROCESSING, NULL)) {
		IMP_LOG_DBG(TAG, "ioctl :IOCTL_AES_START_DE_PROCESSING error!\n");
		return FAILURE;
	}
	memcpy(dstAddr, para[hCipher].dst_addr_v, para[hCipher].dataLen);

	pthread_mutex_unlock(&cipher_mutex);

	return 0;
}

